﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;

using Microsoft.Win32;

using MVPlan.Engine;
using MVPlan.Engine.Dive;
using MVPlan.Engine.Gases;
using MVPlan.Engine.Modeling;
using MVPlan.Engine.Segment;
using Infragistics.Windows.Editors;
using Infragistics.Windows.Chart;
using Infragistics.Windows.Ribbon;
using Infragistics.Excel;

using System.Windows.Threading;
using System.Threading;

namespace OneStop.UI
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class MainControl : UserControl
    {
        #region Members

        public BindingList<SegmentDive> segments = new BindingList<SegmentDive>();
        public BindingList<Gas> gases = new BindingList<Gas>();

        #endregion

        #region Properties

        public bool IsPrimaryView
        {
            get;
            set;
        }

        public Profile Primary { get; set; }
        public Profile Bailout { get; set; }
        public new bool IsLoaded { get; set; }
        public string Path { get; set; }

        #endregion

        #region Constructors

        public MainControl()
        {
            InitializeComponent();
        }

        #endregion

        #region Events

        public event EventHandler FileLoaded;
        public event EventHandler Calculated;
        public event CalculateFailedHandler CalculateFailed;
        public event EventHandler ValidationFailed;

        public delegate void CalculateFailedHandler(object sender, ProfileStatus status);

        protected void OnFileLoaded(object sender)
        {
            if (this.FileLoaded != null)
            {
                FileLoaded(this, EventArgs.Empty);
            }
        }

        protected void OnCalculated(object sender)
        {
            if (this.Calculated != null)
            {
                Calculated(this, EventArgs.Empty);
            }
        }

        protected void OnValidationFailed(object sender)
        {
            if (this.ValidationFailed != null)
            {
                ValidationFailed(this, EventArgs.Empty);
            }
        }

        protected void OnCalculateFailed(object sender, ProfileStatus status)
        {
            if (this.CalculateFailed != null)
            {
                CalculateFailed(this, status);
            }
        }

        #endregion

        #region Methods

        public void SetView(bool primary)
        {
            if (primary)
            {
                Outputs.DataSource = this.Primary.OutputSegments;
                grpOutput.Header = "Output Dive Profile";                
            }
            else
            {
                Outputs.DataSource = this.Bailout.OutputSegments;
                grpOutput.Header = "Output Bailout Profile";
            }

            this.IsPrimaryView = primary;
        }

        public void Reset(bool resetInput)
        {
            if (this.Primary != null)
            {
                this.Primary.Reset();                
            }

            Outputs.DataSource = null;
            
            ProfileChart.Visibility = Visibility.Hidden;
            DiveInfoGrid.Visibility = Visibility.Hidden;

            if (resetInput)
            {
                this.Primary.InputSegments.Clear();
                this.Primary.OutputSegments.Clear();
                this.Primary.Gases.Clear();
                segments.Clear();
                gases.Clear();
            }
        }


        public void LoadFile(string path)
        {
            this.Path = path;
            XDocument mvplan = XDocument.Load(path);

            XElement root = mvplan.Elements().ToList<XElement>()[0];

            XElement prefs = root.Element("Prefs");

            using (MemoryStream p = new MemoryStream())
            {
                StreamWriter w = new StreamWriter(p);
                w.Write(prefs.ToString());
                w.Flush();
                p.Position = 0;

                Prefs.Deserialize(p);
            }

            XElement profile = root.Element("Profile");

            using (MemoryStream p = new MemoryStream())
            {
                StreamWriter w = new StreamWriter(p);
                w.Write(profile.ToString());
                w.Flush();
                p.Position = 0;

                XmlSerializer x = new XmlSerializer(typeof(Profile));

                this.Primary = (Profile)x.Deserialize(p);

                segments = new BindingList<SegmentDive>();

                foreach (SegmentDive s in this.Primary.InputSegments)
                {
                    segments.Add(s);
                }

                gases = new BindingList<Gas>();

                foreach (Gas g in this.Primary.Gases)
                {
                    gases.Add(g);
                }

                ComboBoxItemsProvider ip = new ComboBoxItemsProvider();
                ip.ItemsSource = gases;
                ip.ValuePath = "ShortName";
                ip.DisplayMemberPath = "ShortName";

                if (this.Resources["ComboEditorList"] == null)
                {
                    this.Resources.Add("ComboEditorList", ip);
                }
                else
                {
                    this.Resources["ComboEditorList"] = ip;
                }

                Gases.DataSource = gases;
                Inputs.DataSource = segments;
            }

            this.IsLoaded = true;

            OnFileLoaded(this);
        }

        private bool ValidateInput()
        {
            bool valid = true;

            foreach (SegmentDive s in segments)
            {
                if (s.Depth < 0)
                {
                    this.Inputs.Records[segments.IndexOf(s)].IsSelected = true;
                    MessageBox.Show("Invalid waypoint: depth must be greater than zero");
                    return false;
                }

                if (s.Time < 0)
                {
                    this.Inputs.Records[segments.IndexOf(s)].IsSelected = true;
                    MessageBox.Show("Invalid waypoint: time must be greater than zero");
                    return false;
                }

                if (s.Gas == null)
                {
                    this.Inputs.Records[segments.IndexOf(s)].IsSelected = true;
                    MessageBox.Show("Invalid waypoint: a gas must be selected");
                    return false;
                }
                else
                {
                    if (!gases.IsGasEnabled(s.Gas))
                    {
                        this.Inputs.Records[segments.IndexOf(s)].IsSelected = true;
                        MessageBox.Show("Invalid waypoint: the selected gas is not enabled");
                        return false;
                    }

                    if (!Gas.Validate(s.Gas.FHe, s.Gas.FO2, Prefs.Current.PPO2Max))
                    {
                        this.Inputs.Records[segments.IndexOf(s)].IsSelected = true;
                        MessageBox.Show("Invalid waypoint: the selected gas is invalid");
                        return false;
                    }
                }
            }

            return valid;
        }

        private delegate void DoCalcDelegate(Profile p);
        private delegate void DoCalcFailedDelegate(ProfileStatus status);
        private delegate void UpdateCalcUIDelegate(List<Profile> profiles);

        public void Calculate()
        {
            if (Inputs.ActiveCell != null)
            {
                Inputs.ActiveCell.EndEditMode();
            }

            if (Gases.ActiveCell != null)
            {
                Gases.ActiveCell.EndEditMode();
            }

            if (ValidateInput())
            {
                DoCalcDelegate fetcher = new DoCalcDelegate(this.DoCalc);

                this.Primary = new Profile(segments.ToList<SegmentDive>(), gases.ToList<Gas>(), null);
                this.Primary.IsOCDeco = (bool)chkOCDeco.IsChecked;
                this.Primary.Model.Units = Prefs.Current.Units;

                fetcher.BeginInvoke(this.Primary, null, null);

                //this.IsEnabled = false;
            }
            else
            {
                OnValidationFailed(this);
            }
        }

        void DoCalcFailed(ProfileStatus status)
        {
            OnCalculateFailed(this, status);
        }

        void UpdateCalcUI(List<Profile> profiles)
        {
            this.Primary = profiles[0];

            if (profiles.Count > 1)
                this.Bailout = profiles[1];

            if (this.IsPrimaryView)
            {
                Outputs.DataSource = this.Primary.OutputSegments;
            }
            else
            {
                Outputs.DataSource = this.Bailout.OutputSegments;
            }
            
            if (this.Primary.HasCCRSegment)
            {
                Outputs.FieldLayouts[0].Fields[9].Visibility = Visibility.Visible;
            }
            else
            {
                Outputs.FieldLayouts[0].Fields[9].Visibility = Visibility.Collapsed;
            }

            lblRuntime.Content = string.Format("{0} min", this.Primary.RunTime.ToString("F0"));
            lblDecotime.Content = string.Format("{0} min", this.Primary.DecoTime.ToString("F0"));
            lblOTUs.Content = this.Primary.Model.OxTox.Otu.ToString("F0");
            lblCNS.Content = string.Format("{0} %", (this.Primary.Model.OxTox.Cns * 100).ToString("F0"));
            lblGF.Content = string.Format("{0}-{1}", (this.Primary.Model.Gradient.GfLow * 100).ToString("F0"), (this.Primary.Model.Gradient.GfHigh * 100).ToString("F0"));

            ProfileChart.Visibility = Visibility.Visible;
            DiveInfoGrid.Visibility = Visibility.Visible;

            ProfileChart.Series.Clear();

            BindChart(ProfileChart, this.Primary, true);

            if (this.Primary.HasCCRSegment)
            {
                BindChart(ProfileChart, this.Bailout, false);

                bindGasUsage(this.Bailout, true);

                DiveInfoDetails.RowDefinitions[2].Height = new GridLength(29);
                lblBailDiff.Content = string.Format("{0} min", (this.Bailout.RunTime - this.Primary.RunTime).ToString("F0"));
            }
            else
            {
                bindGasUsage(this.Primary, false);
                DiveInfoDetails.RowDefinitions[2].Height = new GridLength(0);
            }

            OnCalculated(this);
        }

        void DoCalc(Profile primary)
        {            
            ProfileStatus statusp = primary.DoDive();

            if (statusp == ProfileStatus.Success)
            {
                List<Profile> profiles = new List<Profile>();

                profiles.Add(primary);

                Profile bailout = null;

                if (primary.HasCCRSegment)
                {
                    bailout = (Profile)primary.Clone();

                    bailout.SetGases(gases.ToList<Gas>());
                    bailout.Reset();

                    bailout.Model.Gradient.GfLow = Prefs.Current.GfLowBailout;
                    bailout.Model.Gradient.GfHigh = Prefs.Current.GfHighBailout;
                    bailout.IsOCDeco = true;

                    ProfileStatus status = bailout.DoDive();

                    if (status != ProfileStatus.Success)
                    {
                        Root.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new DoCalcFailedDelegate(this.DoCalcFailed), status);
                    }

                    profiles.Add(bailout);
                }
                
                Root.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new UpdateCalcUIDelegate(this.UpdateCalcUI), profiles);
            }
            else
            {
                Root.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new DoCalcFailedDelegate(this.DoCalcFailed), statusp);                
            }
        }

        private void bindGasUsage(Profile p, bool isBailout)
        {
            p.DoGasCalcs();

            string label = "{0} Gas Usage:";

            if (isBailout)
            {
                lblGasUsage.Content = string.Format(label, "Bailout");
            }
            else
            {
                lblGasUsage.Content = string.Format(label, "Dive");
            }

            p.Gases.Sort(new GasVolumeComparer(GasVolumeComparer.SortOrder.Descending));

            GasStack.Children.Clear();

            foreach (Gas g in p.Gases)
            {
                if (g.Volume > 0)
                {
                    System.Windows.Controls.Label l = new System.Windows.Controls.Label();
                    l.Content = string.Format("{0}: {1} litres", g.ShortName, g.Volume.ToString("F0"));
                    l.Style = (Style)DiveInfoGrid.Resources["DiveInfo"];
                    GasStack.Children.Add(l);
                }
            }
        }

        public void BindChart(XamChart chart,  Profile p, bool isPrimary)
        {
            chart.RefreshEnabled = false;

            Series sr = new Series();

            sr.ChartType = ChartType.ScatterLine;

            if (isPrimary)
            {
                sr.Label = string.Format("Dive ({0}-{1})", this.Primary.Model.Gradient.GfLow.FormatGf(), this.Primary.Model.Gradient.GfHigh.FormatGf());
                sr.Fill = Brushes.Blue;
            }
            else
            {
                sr.Label = string.Format("Bailout ({0}-{1})", this.Bailout.Model.Gradient.GfLow.FormatGf(), this.Bailout.Model.Gradient.GfHigh.FormatGf());
                sr.Fill = Brushes.Red;
            }

            Animation a = new Animation();
            a.Style = (Style)this.Resources["LineAnimation"];

            sr.Animation = a;

            chart.Series.Add(sr);

            DataPoint x = new DataPoint();

            x.ChartParameters.Add(ChartParameterType.ValueX, 0);
            x.ChartParameters.Add(ChartParameterType.ValueY, 0);

            sr.DataPoints.Add(x);

            foreach (SegmentAbstract s in p.OutputSegments)
            {       
                if (s.Type == SegmentType.DECO)
                {
                    DataPoint d = new DataPoint();

                    d.ChartParameters.Add(ChartParameterType.ValueX, s.RunTime - s.Time);
                    d.ChartParameters.Add(ChartParameterType.ValueY, s.Depth * -1);

                    sr.DataPoints.Add(d);
                    
                    d.Marker = new Marker();
                    d.ToolTip = s.ToString();
                    d.Marker.Type = MarkerType.Circle;
                    d.Marker.Format = " ";
                    d.Marker.Fill = Brushes.White;
                    d.Marker.Stroke = Brushes.Black;

                    if (s.Type == SegmentType.GASSWITCH)
                    {
                        d.Marker.Type = MarkerType.Star5;
                    }

                    DataPoint e = new DataPoint();

                    e.ChartParameters.Add(ChartParameterType.ValueX, s.RunTime);
                    e.ChartParameters.Add(ChartParameterType.ValueY, s.Depth * -1);

                    sr.DataPoints.Add(e);
                }
                else
                {

                    DataPoint d = new DataPoint();

                    d.ChartParameters.Add(ChartParameterType.ValueX, s.RunTime);
                    d.ChartParameters.Add(ChartParameterType.ValueY, s.Depth * -1);

                    sr.DataPoints.Add(d);

                    d.Marker = new Marker();
                    d.ToolTip = s.ToString();
                    d.Marker.Type = MarkerType.Circle;
                    d.Marker.Format = " ";
                    d.Marker.Fill = Brushes.White;
                    d.Marker.Stroke = Brushes.Black;

                    if (s.Type == SegmentType.GASSWITCH)
                    {
                        d.Marker.Type = MarkerType.Star5;
                    }
                }
            }

            DataPoint z = new DataPoint();

            z.ChartParameters.Add(ChartParameterType.ValueX, p.RunTime + 1);
            z.ChartParameters.Add(ChartParameterType.ValueY, 0);

            sr.DataPoints.Add(z);

            if (chart.Series.Count == 2)
            {
                Series ps = chart.Series[0];
                chart.Series.RemoveAt(0);
                chart.Series.Add(ps);
            }

            double unit = 5;

            if (p.RunTime > 60)
                unit = 10;
            if (p.RunTime > 120)
                unit = 15;
            if (p.RunTime > 140)
                unit = 20;

            chart.Axes[0].Unit = unit;

            chart.RefreshEnabled = true;
        }
        
        #endregion

        #region Grid Events
        
        private void Inputs_EditModeEnding(object sender, Infragistics.Windows.DataPresenter.Events.EditModeEndingEventArgs e)
        {
            if (e.Editor.ValueType == typeof(Gas))
            {
                XamComboEditor editor = (XamComboEditor)e.Editor;
                e.Editor.Value = editor.ComboBox.SelectionBoxItem;
            }
        }

        private void InputsDelete_Click(object sender, RoutedEventArgs e)
        {
            segments.RemoveAt(Inputs.ActiveRecord.Index);
        }

        private void Inputs_RecordUpdated(object sender, Infragistics.Windows.DataPresenter.Events.RecordUpdatedEventArgs e)
        {
            Inputs.DataSource = null;
            Inputs.DataSource = segments;
        }

        private void GasesDelete_Click(object sender, RoutedEventArgs e)
        {
            gases.RemoveAt(Gases.ActiveRecord.Index);
        }

        void Gases_RecordUpdated(object sender, Infragistics.Windows.DataPresenter.Events.RecordUpdatedEventArgs e)
        {
            Gases.DataSource = null;
            Gases.DataSource = gases;
        }

        #endregion        

        public void ResetGases()
        {
            Gases.DataSource = null;

            foreach (Gas g in gases)
            {
                g.MOD = Util.CalcMod(g.FO2);
            }

            Gases.DataSource = gases;
        }
    }
}
